// Store.js
const store = {},
  win = (typeof window != 'undefined' ? window : global),
  doc = win.document,
  localStorageName = 'localStorage',
  scriptTag = 'script',
  storage

store.disabled = false
store.version = '1.3.20'
store.set = function(key, value) {}
store.get = function(key, defaultVal) {}
store.has = function(key) { return store.get(key) !== undefined }
store.remove = function(key) {}
store.clear = function() {}
store.transact = function(key, defaultVal, transactionFn) {
  if (transactionFn == null) {
    transactionFn = defaultVal
    defaultVal = null
  }
  if (defaultVal == null) {
    defaultVal = {}
  }
  var val = store.get(key, defaultVal)
  transactionFn(val)
  store.set(key, val)
}
store.getAll = function() {}
store.forEach = function() {}

store.serialize = function(value) {
  return JSON.stringify(value)
}
store.deserialize = function(value) {
  if (typeof value != 'string') { return undefined }
  try { return JSON.parse(value) }
  catch(e) { return value || undefined }
}

// Functions to encapsulate questionable FireFox 3.6.13 behavior
// when about.config::dom.storage.enabled === false
// See https://github.com/marcuswestin/store.js/issues#issue/13
function isLocalStorageNameSupported() {
  try { return (localStorageName in win && win[localStorageName]) }
  catch(err) { return false }
}

if (isLocalStorageNameSupported()) {
  storage = win[localStorageName]
  store.set = function(key, val) {
    if (val === undefined) { return store.remove(key) }
    storage.setItem(key, store.serialize(val))
    return val
  }
  store.get = function(key, defaultVal) {
    var val = store.deserialize(storage.getItem(key))
    return (val === undefined ? defaultVal : val)
  }
  store.remove = function(key) { storage.removeItem(key) }
  store.clear = function() { storage.clear() }
  store.getAll = function() {
    var ret = {}
    store.forEach(function(key, val) {
      ret[key] = val
    })
    return ret
  }
  store.forEach = function(callback) {
    for (var i=0; i<storage.length; i++) {
      var key = storage.key(i)
      callback(key, store.get(key))
    }
  }
} else if (doc && doc.documentElement.addBehavior) {
  var storageOwner,
    storageContainer
  // Since #userData storage applies only to specific paths, we need to
  // somehow link our data to a specific path.  We choose /favicon.ico
  // as a pretty safe option, since all browsers already make a request to
  // this URL anyway and being a 404 will not hurt us here.  We wrap an
  // iframe pointing to the favicon in an ActiveXObject(htmlfile) object
  // (see: http://msdn.microsoft.com/en-us/library/aa752574(v=VS.85).aspx)
  // since the iframe access rules appear to allow direct access and
  // manipulation of the document element, even for a 404 page.  This
  // document can be used instead of the current document (which would
  // have been limited to the current path) to perform #userData storage.
  try {
    storageContainer = new ActiveXObject('htmlfile')
    storageContainer.open()
    storageContainer.write('<'+scriptTag+'>document.w=window</'+scriptTag+'><iframe src="/favicon.ico"></iframe>')
    storageContainer.close()
    storageOwner = storageContainer.w.frames[0].document
    storage = storageOwner.createElement('div')
  } catch(e) {
    // somehow ActiveXObject instantiation failed (perhaps some special
    // security settings or otherwse), fall back to per-path storage
    storage = doc.createElement('div')
    storageOwner = doc.body
  }
  var withIEStorage = function(storeFunction) {
    return function() {
      var args = Array.prototype.slice.call(arguments, 0)
      args.unshift(storage)
      // See http://msdn.microsoft.com/en-us/library/ms531081(v=VS.85).aspx
      // and http://msdn.microsoft.com/en-us/library/ms531424(v=VS.85).aspx
      storageOwner.appendChild(storage)
      storage.addBehavior('#default#userData')
      storage.load(localStorageName)
      var result = storeFunction.apply(store, args)
      storageOwner.removeChild(storage)
      return result
    }
  }

  // In IE7, keys cannot start with a digit or contain certain chars.
  // See https://github.com/marcuswestin/store.js/issues/40
  // See https://github.com/marcuswestin/store.js/issues/83
  var forbiddenCharsRegex = new RegExp("[!\"#$%&'()*+,/\\\\:;<=>?@[\\]^`{|}~]", "g")
  var ieKeyFix = function(key) {
    return key.replace(/^d/, '___$&').replace(forbiddenCharsRegex, '___')
  }
  store.set = withIEStorage(function(storage, key, val) {
    key = ieKeyFix(key)
    if (val === undefined) { return store.remove(key) }
    storage.setAttribute(key, store.serialize(val))
    storage.save(localStorageName)
    return val
  })
  store.get = withIEStorage(function(storage, key, defaultVal) {
    key = ieKeyFix(key)
    var val = store.deserialize(storage.getAttribute(key))
    return (val === undefined ? defaultVal : val)
  })
  store.remove = withIEStorage(function(storage, key) {
    key = ieKeyFix(key)
    storage.removeAttribute(key)
    storage.save(localStorageName)
  })
  store.clear = withIEStorage(function(storage) {
    var attributes = storage.XMLDocument.documentElement.attributes
    storage.load(localStorageName)
    for (var i=attributes.length-1; i>=0; i--) {
      storage.removeAttribute(attributes[i].name)
    }
    storage.save(localStorageName)
  })
  store.getAll = function(storage) {
    var ret = {}
    store.forEach(function(key, val) {
      ret[key] = val
    })
    return ret
  }
  store.forEach = withIEStorage(function(storage, callback) {
    var attributes = storage.XMLDocument.documentElement.attributes
    for (var i=0, attr; attr=attributes[i]; ++i) {
      callback(attr.name, store.deserialize(storage.getAttribute(attr.name)))
    }
  })
}

try {
  var testKey = '__storejs__'
  store.set(testKey, testKey)
  if (store.get(testKey) != testKey) { store.disabled = true }
  store.remove(testKey)
} catch(e) {
  store.disabled = true
}
store.enabled = !store.disabled
